package com.qianxun.watchdog.util;

import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;

public class FileUtil {

    public static final char SEPARATOR_CHAR = '/' ;

    public static final String SEPARATOR = "" + SEPARATOR_CHAR;


	public static String classPath(){
        String temp = FileUtil.class.getResource("").getPath();
        String suffix = SEPARATOR + FileUtil.class.getName().replace("." ,SEPARATOR).replace(FileUtil.class.getSimpleName() ,"");
        temp = temp.replace(suffix ,"");
        if(temp.endsWith("!")){
            temp = temp.replace("!" ,"").replace("file:" + SEPARATOR ,"");
        }else{
            temp += SEPARATOR;
        }
        return temp;
    }


    public static Set<String> fileListAsRecursion(String dir){
        return fileListAsRecursion(dir ,null);
    }

    public static Set<String> fileListAsRecursion(String dir ,Filter filter) {
        if (dir == null || dir.trim().equals("")) {
            return new HashSet<>(0);
        }
        dir = switchSeparator(dir);

        File dirFile = new File(dir);
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }
        String root = classPath();

        if(root.endsWith(".jar")){
            Set<String> res = new HashSet<>(16);
            String jarPath = dir.startsWith("classpath:") ? dir.replace("classpath:" ,"") : dir;
            JarFile jar = null;
            try {
                jar = new JarFile(new File(root));
            } catch (IOException e) {
                e.printStackTrace();
                return new HashSet<>(0);
            }
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String pathName = entry.getName();
                if (pathName.charAt(0) == SEPARATOR_CHAR) {
                    pathName = pathName.substring(1);
                }
                String absPath = root + "!/" + pathName;
                String name = pathName.lastIndexOf(SEPARATOR) != -1 ? pathName.substring(pathName.lastIndexOf(SEPARATOR) + 1) : pathName;
                if(pathName.startsWith(jarPath) && filter(filter ,absPath ,pathName ,name)){
                    res.add("jar:file:" + absPath);
                }
            }
            return res;
        }

        String targetPath = dir.startsWith("classpath:")
                ? root + dir.substring(dir.indexOf(":") + 1)
                : root.replace(wrapPath("target" ,"classes"),"") + dir;
        dirFile = new File(targetPath);
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        dirFile = new File(targetPath.replace(wrapPath("resources"), ""));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        if(root.indexOf("WEB-INF") == -1) {
            return new HashSet<>(0);
        }

        String webRoot = root.substring(0, root.indexOf("WEB-INF"));
        String webPath = dir.startsWith("classpath:") ? wrapPath("WEB-INF" ,"classes") + dir.substring(dir.indexOf(":") + 1) : dir;
        dirFile = new File(wrapPath(webRoot ,webPath));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        String webPath2 = webPath.replace(wrapPath("resources"), "");
        dirFile = new File(wrapPath(webRoot ,webPath2));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        String webPath3 = dir.startsWith(wrapPath("src" ,"main" ,"webapp"))
                ? detectAndGetPathName(root, wrapPath("src" ,"main" ,"webapp")) + dir.substring(dir.indexOf(wrapPath("src" ,"main" ,"webapp")) + wrapPath("src" ,"main" ,"webapp").length())
                : dir;
        dirFile = new File(wrapPath(webRoot ,webPath3));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }
        return new HashSet<>(0);
    }

    public static Set<String> fileListAsRecursion(File dirFile){
        return fileListAsRecursion(dirFile ,null);
    }

    public static Set<String> fileListAsRecursion(File dirFile ,Filter filter){
        if(dirFile == null || !dirFile.exists()){
            return new HashSet<>(0);
        }
        if(dirFile.isFile()){
            if(filter(filter ,dirFile.getAbsolutePath() ,dirFile.getPath() ,dirFile.getName())) {
                return Stream.of(dirFile.getAbsolutePath()).collect(HashSet::new, (res, ele) -> res.add(switchSeparator(ele)), (l, r) -> {
                });
            }
            return new HashSet<>(0);
        }
        Set<String> res = new HashSet<>();
        File[] files = dirFile.listFiles();
        for(int i = 0 ;i < files.length ;++i){
            Set<String> children = fileListAsRecursion(files[i] ,filter);
            if(children != null) {
                res.addAll(children);
            }
        }
        return res;
    }

    public static byte[] read(String pathName) throws IOException {
        if (pathName == null || pathName.trim().equals("")) {
            throw new IOException("file failed pathName:" + pathName);
        }
        pathName = switchSeparator(pathName);
        try {
            return read(new File(pathName));
        } catch (IOException e) {
            // TODO: handle exception
        }

        String root = classPath();
        if(root.endsWith(".jar") || pathName.startsWith("jar:file:") || pathName.indexOf(".jar!/") != -1){
            String temp = pathName;
            if(!pathName.startsWith("jar:file:") && pathName.indexOf(".jar!/") == -1){
                String jarPath = temp.startsWith("classpath:") ? temp.replace("classpath:" ,"") : temp;
                temp = root + "!/" + jarPath;
            }
            String targetPath = temp.startsWith("jar:file:") ? temp : "jar:file:" + temp;
            InputStream inputStream = ((JarURLConnection)new URL(targetPath).openConnection()).getInputStream();
            if(inputStream == null){
                throw new IOException("file not found pathName:" + pathName);
            }
            return read(inputStream);
        }

        String targetPath = pathName.startsWith("classpath:")
                ? root + pathName.substring(pathName.indexOf(":") + 1)
                : root.replace(wrapPath("target" ,"classes") ,"") + pathName;
        try {
            return read(new File(targetPath));
        } catch (IOException e) {
            // TODO: handle exception
        }

        try {
            return read(new File(targetPath.replace(wrapPath("resources"), "")));
        } catch (IOException e) {
            // TODO: handle exception
        }

        if(root.indexOf("WEB-INF") == -1) {
            throw new IOException("file not found pathName:" + pathName);
        }

        String webRoot = root.substring(0, root.indexOf("WEB-INF"));

        String webPath = pathName.startsWith("classpath:") ? wrapPath("WEB-INF" ,"classes") + pathName.substring(pathName.indexOf(":") + 1) : pathName;

        File file = new File(wrapPath(webRoot ,webPath));
        try {
            return read(file);
        } catch (IOException e) {
            // TODO: handle exception
        }

        String webPath2 = webPath.replace(wrapPath("resources"), "");
        file = new File(wrapPath(webRoot ,webPath2));
        try {
            return read(file);
        } catch (IOException e) {
            // TODO: handle exception
        }

        String webPath3 = pathName.startsWith(wrapPath("src" ,"main" ,"webapp"))
                ? detectAndGetPathName(root, wrapPath("src" ,"main" ,"webapp")) + pathName.substring(pathName.indexOf(wrapPath("src" ,"main" ,"webapp")) + wrapPath("src" ,"main" ,"webapp").length())
                : pathName;
        file = new File(wrapPath(webRoot ,webPath3));
        return read(file);
    }


    public static byte[] read(File file) throws IOException {
        if (file == null || !file.exists() || file.isDirectory()) {
            throw new IOException("failed file :" + file);
        }
        return read(new FileInputStream(file));
    }

    public static byte[] read(InputStream is) throws IOException {
        byte[] res;
        int i = is.available();
        if(i > 0){
            res = new byte[i];
            is.read(res);
        }else {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] temp = new byte[1024];
            int n = 0;
            while ((n = is.read(temp)) != -1){
                byteArrayOutputStream.write(temp ,0 ,n);
            }
            res = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.close();
        }
        is.close();
        return res;
    }


    public static void write(String path, byte[] buf) throws IOException {
        write(path, buf ,false);
    }


    public static void write(String path ,byte[] buf ,boolean append) throws IOException {
        write(new File(path) ,buf ,append);
    }


    public static void write(File file ,byte[] buf)throws IOException{
        write(file ,buf ,false);
    }


    public static void write(File file ,byte[] buf ,boolean append)throws IOException{
        write(new FileOutputStream(file ,append) ,buf);
    }


    public static void write(OutputStream os ,byte[] buf) throws IOException {
        os.write(buf);
        os.flush();
        os.close();
    }


    public static String switchSeparator(String path) {
        return path.replace("\\\\", SEPARATOR).replace("\\" ,SEPARATOR);
    }


    public static String wrapPath(String... pathElements){
        StringBuffer sb = new StringBuffer();
        if(pathElements != null && pathElements.length > 0){
            for(String ele : pathElements){
                sb.append(ele).append(SEPARATOR);
            }
        }
        return sb.toString();
    }

    private static boolean filter(Filter filter,String absPath ,String relativePath ,String fileName){
        return filter == null ? true : filter.filter(absPath ,relativePath ,fileName);
    }


    private static String detectAndGetPathName(String root ,String target) {
        return detectAndGetPathName(root, target, "");
    }

    private static String detectAndGetPathName(String root ,String target ,String defaultValue) {
        File file = new File(root + SEPARATOR + target);
        if(file.exists()) {
            return target;
        }
        return defaultValue;
    }


    @FunctionalInterface
    public interface Filter{
        boolean filter(String absPath, String relativePath, String fileName);
    }
}
